from django.forms import Widget, Field, MultiValueField
from django.forms.util import ErrorList
from django.core import validators
from django.core.exceptions import ValidationError
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _, ungettext_lazy
import django.utils.copycompat as copy

from itertools import chain, repeat, izip as zip
try:
    from itertools import izip_longest as zipl
except ImportError:
    #a much more lighter version of izip_longest that works with two sequence arguments
    def zipl(seq1, seq2):
        return zip(chain(seq1, repeat(None, len(seq2) - len(seq1))),
                   chain(seq2, repeat(None, len(seq1) - len(seq2))))

#-----------------------------------------Widgets-------------------------------------------

class ClonerWidget(Widget):
    class Media:
        js = ('/dynforms.js',)
    
    #args are (root_id, counter_id, branch_tag, clone, max_clones)
    button_onclick_f = u"clonerWidget$clone('%s', '%s', '%s', '%s', %d);"
    cc_id_f = u'%s_clone_container'
    button_id_f = u'%s_button'
    counter_id_f = u'%s_counter'
    counter_name_f = u'%s_counter'   
    
    def __init__(self, cloned_widget, init_clones, max_clones, button_label, attrs=None):
        super(ClonerWidget, self).__init__(attrs)
        self.cloned_widget = cloned_widget
        self.init_clones = init_clones
        self.max_clones = max_clones
        self.button_label = escape(button_label) #escape is lazy
    
    def render(self, name, value, attrs=None):
        #if value: raise Exception(repr(value))
        cloned_widget = self.cloned_widget
        if self.is_localized:
            cloned_widget.is_localized = True
        if value is None:
            value = []
        final_attrs = self.build_attrs(attrs)
        id_ = final_attrs.get('id', '')
        cw_id = id_ + '_%s'
        cw_name = name + '_%s'
        id_ = force_unicode(id_)
        cc_id = self.cc_id_f % id_
        button_id = self.button_id_f % id_
        counter_id = self.counter_id_f % id_
        counter_name = self.counter_name_f % name
        
        output = [self.cloned_widget.render(cw_name % i, v, attrs=dict(final_attrs, id=cw_id % i)) 
            for i, v in enumerate(value)]
        output.extend(self.cloned_widget.render(cw_name % i, None, attrs=dict(final_attrs, id=cw_id % i)) 
            for i in xrange(len(value), self.init_clones))
        
        clone = self.cloned_widget.render(cw_name % '{0}', None, attrs=dict(final_attrs, id=cw_id % '{0}'))
        return mark_safe(self.format_output(output, cc_id, button_id, 
                                            counter_id, counter_name, escape(clone)))
    
    @classmethod
    def id_for_label(self, id_):
        if id_:
            id_ += '_0'
        return id_
    
    def value_from_datadict(self, data, files, name): #this makes the use of izip_longest unnecessary
        counter_name = self.counter_name_f % name
        cw_name = name + '_%s'
        count = data.get(counter_name, 0)
        try:
            count = int(count)
        except ValueError:
            return []
        
        cloned_widget = self.cloned_widget
        return [cloned_widget.value_from_datadict(data, files, cw_name % i) for i in xrange(count)]

    def _has_changed(self, initial, data):
        cloned_widget = self.cloned_widget
        initial = initial or []
        for i, d in zipl(initial, data):
            if cloned_widget._has_changed(i, d):
                return True
        return False

    def format_output(self, rendered, cc_id, button_id, counter_id, counter_name, clone):
        sub_tag = u'li'
        start = u'<ol id="%s">' % cc_id
        button_onclick = self.button_onclick_f % (cc_id, counter_id, sub_tag, clone, self.max_clones)
        end = (u'</ol><input type="hidden" id="%s" name="%s" value="%s"/>'
               u'<input type="button" id="%s" onclick="%s" value="%s"/>' %
               (counter_id, counter_name, self.init_clones, button_id,  button_onclick, self.button_label))
        tagged_f = u'<' + sub_tag + u'>' + u'%s' + u'</' + sub_tag + u'>' 
        return start + u'\n'.join(tagged_f % r for r in rendered) + end
    
    @property
    def _get_media(self):
        return self.cloned_widget.media

    def __deepcopy__(self, memo):
        obj = super(ClonerWidget, self).__deepcopy__(memo)
        obj.cloned_widget = copy.deepcopy(self.cloned_widget)
        return obj 
        
#-----------------------------------------Fields-------------------------------------------

class ClonerField(Field):
    widget = ClonerWidget
    button_label = _('Append Field')
    invalid_field_msg = _('Input to field %(number)s is invalid')
    def __init__(self, cloned_field, init_clones=1, max_clones=None, min_clones=None,
                 button_label=None, *args, **kwargs):
        button_label = button_label or self.button_label
        init_clones = max(init_clones, min_clones)
        self.cloned_field = cloned_field
        self.cloned_field.required = False
        widget = (kwargs.get('widget') or 
                  self.widget(cloned_field.widget, init_clones, max_clones, self.button_label))
        kwargs.update(widget=widget)
        super(ClonerField, self).__init__(*args, **kwargs)
        if min_clones is not None:
            validator = validators.MinLengthValidator(min_clones)
            validator.message = ungettext_lazy('Give at least %(limit_value)s choice.', #!! will this work?
                                               'Give at least %(limit_value)s choices.', min_clones)
            self.validators.append(validator)
        if max_clones is not None:
            validator = validators.MaxLengthValidator(max_clones)
            validator.message = ungettext_lazy('Give at most %(limit_value)s choice.', #!! will this work?
                                               'Give at most %(limit_value)s choices.', max_clones)
            self.validators.append(validator)
    
    def to_python(self, value):
        value = super(ClonerField, self).to_python(value)
        return [v for v in value if v not in validators.EMPTY_VALUES]
    
    def clean(self, value):
        value = super(ClonerField, self).clean(value)
        field = self.cloned_field
        errors = ErrorList()
        for i, fv in enumerate(value):
            try:
                value[i] = field.clean(fv)
            except ValidationError, e:
                invalid_field_msg = force_unicode(self.invalid_field_msg % i)
                errors.append(invalid_field_msg) #Message indicating the field where the errors are
                errors.extend(e.messages)
        if errors:
            raise ValidationError(errors)
        return value
    